/*
 ------------------------------------------------------------------------------
 Copyright (C) 2013 Eternal Games.

 This file is part of the EternalTech source code.

 The EternalTech source code is free software; you can redistribute it and/or
 modify it under the terms of the GNU General Public License as published by
 the Free Software Foundation; either version 2 of the License, or (at your
 option) any later version.

 The EternalTech source code is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 more details.

 You should have received a copy of the GNU General Public License along with
 the EternalTech source code; if not, write to the Free Software Foundation, Inc.,
 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 ------------------------------------------------------------------------------
*/


//
// netChan.cpp - network channel
//


#include "common.h"


static const char *			net_sourceString[2] = {"client", "server"};

static glqRandom			net_random;

static cvar_t *				net_dropSim;
static cvar_t *				net_showDrop;
static cvar_t *				net_showPackets;
static cvar_t *				net_showHuffman;
static cvar_t *				net_channelPort;


/*
 ==================
 NetChan_Init
 ==================
*/
void NetChan_Init (){

	// Register variables
	net_dropSim = CVar_Register("net_dropSim", "0.0", CVAR_FLOAT, CVAR_CHEAT, "Simulate packet dropping", 0.0f, 1.0f);
	net_showDrop = CVar_Register("net_showDrop", "0", CVAR_BOOL, CVAR_CHEAT, "Show packet dropping", 0, 0);
	net_showPackets = CVar_Register("net_showPackets", "0", CVAR_BOOL, CVAR_CHEAT, "Show incoming/outgoing packets", 0, 0);
	net_showHuffman = CVar_Register("net_showHuffman", "0", CVAR_BOOL, CVAR_CHEAT, "Show Huffman compression/decompression", 0, 0);
	net_channelPort = CVar_Register("net_channelPort", Str_FromInteger(Sys_Milliseconds() & 0xFFFF), CVAR_INTEGER, CVAR_READONLY, "Network channel port", 0, 65535);

	// Set an initial seed for the random number generator
	net_random.SetSeed(Sys_Milliseconds());
}

/*
 ==================
 NetChan_Setup
 ==================
*/
void NetChan_Setup (netChan_t *netChan, netSrc_t sock, const netAdr_t adr, int channelPort){

	Mem_Fill(netChan, 0, sizeof(netChan_t));

	netChan->sock = sock;
	netChan->remoteAddress = adr;
	netChan->channelPort = channelPort;

	netChan->incomingSequence = 0;
	netChan->outgoingSequence = 1;
}

/*
 ==================
 NetChan_Transmit
 ==================
*/
void NetChan_Transmit (netChan_t *netChan, int time, msg_t *msg){

	byte	buffer[MAX_MSG_SIZE];
	byte	data[MAX_PACKET_SIZE];
	int		size;
	msg_t	send;

	if (netChan->outgoingFragments)
		Com_Error(false, "NetChan_Transmit: unsent outgoing fragments");

	// Compress the message data
	size = Huff_Compress(msg->data, buffer, msg->writeBytes);

	if (net_showHuffman->integerValue)
		Com_Printf("%s compress: %4i -> %4i\n", net_sourceString[netChan->sock], msg->writeBytes, size);

	// Fragment large messages
	if (size >= FRAGMENT_SIZE){
		netChan->outgoingFragments = true;
		netChan->outgoingFragmentBytes = msg->writeBytes;
		netChan->outgoingFragmentOffset = 0;
		netChan->outgoingFragmentSize = size;
		Mem_Copy(netChan->outgoingFragmentBuffer, buffer, size);

		// Only send the first fragment now
		NetChan_TransmitNextFragment(netChan, time);

		return;
	}

	// Initialize the message buffer
	MSG_Init(&send, data, sizeof(data), false);

	// Write the sequence number
	MSG_WriteLong(&send, netChan->outgoingSequence);

	// Write the uncompressed message size
	MSG_WriteShort(&send, msg->writeBytes);

	// Write the channel port if we are a client
	if (netChan->sock == NS_CLIENT)
		MSG_WriteWord(&send, net_channelPort->integerValue);

	// Write the compressed message data
	MSG_WriteData(&send, buffer, size);

	// Send the datagram
	if (netChan->remoteAddress.type == NA_LOOPBACK)
		NET_SendLoopPacket(netChan->sock, netChan->remoteAddress, send.data, send.writeBytes);
	else
		NET_SendPacket(netChan->sock, netChan->remoteAddress, send.data, send.writeBytes);

	// Update rate variables
	if (time - netChan->outgoingRateTime > 1000){
		netChan->outgoingRateBytes -= netChan->outgoingRateBytes * (time - netChan->outgoingRateTime - 1000) / 1000;
		if (netChan->outgoingRateBytes < 0)
			netChan->outgoingRateBytes = 0;
	}

	netChan->outgoingRateTime = time - 1000;
	netChan->outgoingRateBytes += send.writeBytes;

	if (net_showPackets->integerValue)
		Com_Printf("%s send %4i: seq=%i ack=%i\n", net_sourceString[netChan->sock], send.writeBytes, netChan->outgoingSequence, netChan->incomingSequence);

	netChan->outgoingSequence++;
}

/*
 ==================
 NetChan_TransmitNextFragment
 ==================
*/
void NetChan_TransmitNextFragment (netChan_t *netChan, int time){

	byte	data[MAX_PACKET_SIZE];
	int		fragmentSize;
	msg_t	send;

	if (!netChan->outgoingFragments)
		Com_Error(false, "NetChan_TransmitNextFragment: no outgoing fragments");

	if (netChan->outgoingFragmentOffset + FRAGMENT_SIZE <= netChan->outgoingFragmentSize)
		fragmentSize = FRAGMENT_SIZE;
	else
		fragmentSize = netChan->outgoingFragmentSize - netChan->outgoingFragmentOffset;

	// Initialize the message buffer
	MSG_Init(&send, data, sizeof(data), false);

	// Write the sequence number
	MSG_WriteLong(&send, netChan->outgoingSequence | FRAGMENT_BIT);

	// Write the uncompressed message size
	MSG_WriteShort(&send, netChan->outgoingFragmentBytes);

	// Write the channel port if we are a client
	if (netChan->sock == NS_CLIENT)
		MSG_WriteWord(&send, net_channelPort->integerValue);

	// Write the fragment offset and size
	MSG_WriteShort(&send, netChan->outgoingFragmentOffset);
	MSG_WriteShort(&send, fragmentSize);

	// Write a fragment of the compressed message data
	MSG_WriteData(&send, netChan->outgoingFragmentBuffer + netChan->outgoingFragmentOffset, fragmentSize);

	// Send the datagram
	if (netChan->remoteAddress.type == NA_LOOPBACK)
		NET_SendLoopPacket(netChan->sock, netChan->remoteAddress, send.data, send.writeBytes);
	else
		NET_SendPacket(netChan->sock, netChan->remoteAddress, send.data, send.writeBytes);

	// Update rate variables
	if (time - netChan->outgoingRateTime > 1000){
		netChan->outgoingRateBytes -= netChan->outgoingRateBytes * (time - netChan->outgoingRateTime - 1000) / 1000;
		if (netChan->outgoingRateBytes < 0)
			netChan->outgoingRateBytes = 0;
	}

	netChan->outgoingRateTime = time - 1000;
	netChan->outgoingRateBytes += send.writeBytes;

	if (net_showPackets->integerValue)
		Com_Printf("%s send %4i: seq=%i ack=%i fragment=%i,%i\n", net_sourceString[netChan->sock], send.writeBytes, netChan->outgoingSequence, netChan->incomingSequence, netChan->outgoingFragmentOffset, fragmentSize);

	netChan->outgoingFragmentOffset += fragmentSize;

	// This exit condition is a little tricky, because a packet that is exactly
	// the fragment size still needs to send a second packet of zero length so
	// that the other side can tell there aren't more to follow
	if (netChan->outgoingFragmentOffset == netChan->outgoingFragmentSize && fragmentSize != FRAGMENT_SIZE){
		netChan->outgoingFragments = false;

		netChan->outgoingSequence++;
	}
}

/*
 ==================
 NetChan_TransmitAllFragments
 ==================
*/
void NetChan_TransmitAllFragments (netChan_t *netChan, int time){

	if (!netChan->outgoingFragments)
		Com_Error(false, "NetChan_TransmitAllFragments: no outgoing fragments");

	// Send all the remaining fragments at once
	while (netChan->outgoingFragments)
		NetChan_TransmitNextFragment(netChan, time);
}

/*
 ==================
 NetChan_Process
 ==================
*/
bool NetChan_Process (netChan_t *netChan, int time, msg_t *msg){

	byte	buffer[MAX_MSG_SIZE];
	int		sequence, size;
	int		fragmentOffset, fragmentSize;
	bool	fragmented;
	int		dropped;

	// Allow simulation of connections that drop a lot of packets
	if (net_dropSim->floatValue){
		if (net_random.RandFloat() < net_dropSim->floatValue)
			return false;
	}

	// Clear the read state
	MSG_ClearReadState(msg);

	// Read the sequence number
	sequence = MSG_ReadLong(msg);

	// Check for a fragmented message
	if (sequence & FRAGMENT_BIT){
		sequence &= ~FRAGMENT_BIT;

		fragmented = true;
	}
	else
		fragmented = false;

	// Read the uncompressed message size
	size = MSG_ReadShort(msg);

	// Read the channel port if we are a server
	if (netChan->sock == NS_SERVER)
		MSG_ReadWord(msg);

	// Read the fragment offset and size if this is a fragmented message
	if (fragmented){
		fragmentOffset = MSG_ReadShort(msg);
		fragmentSize = MSG_ReadShort(msg);
	}

	// Update rate variables
	if (time - netChan->incomingRateTime > 1000){
		netChan->incomingRateBytes -= netChan->incomingRateBytes * (time - netChan->incomingRateTime - 1000) / 1000;
		if (netChan->incomingRateBytes < 0)
			netChan->incomingRateBytes = 0;
	}

	netChan->incomingRateTime = time - 1000;
	netChan->incomingRateBytes += msg->writeBytes;

	if (net_showPackets->integerValue){
		if (fragmented)
			Com_Printf("%s recv %4i: seq=%i fragment=%i,%i\n", net_sourceString[netChan->sock], msg->writeBytes, sequence, fragmentOffset, fragmentSize);
		else
			Com_Printf("%s recv %4i: seq=%i\n", net_sourceString[netChan->sock], msg->writeBytes, sequence);
	}

	// Discard out of order or duplicated packets
	if (sequence <= netChan->incomingSequence){
		if (net_showDrop->integerValue)
			Com_Printf("%s: out of order packet %i at %i\n", NET_AddressToString(netChan->remoteAddress), sequence, netChan->incomingSequence);

		return false;
	}

	// Dropped packets don't keep the message from being used
	dropped = sequence - (netChan->incomingSequence + 1);
	if (dropped > 0){
		if (net_showDrop->integerValue)
			Com_Printf("%s: dropped %i packets at %i\n", NET_AddressToString(netChan->remoteAddress), dropped, sequence);
	}

	// Discard packets with bad uncompressed message sizes
	if (size < 0 || size > MAX_MSG_SIZE){
		if (net_showDrop->integerValue)
			Com_Printf("%s: illegal uncompressed message size at %i\n", NET_AddressToString(netChan->remoteAddress), sequence);

		return false;
	}

	// If a fragmented message
	if (fragmented){
		// Make sure we add the fragments in correct order
		if (netChan->incomingFragmentSequence != sequence){
			netChan->incomingFragmentSequence = sequence;
			netChan->incomingFragmentSize = 0;
		}

		// If we missed a fragment, dump it
		if (netChan->incomingFragmentSize != fragmentOffset){
			if (net_showDrop->integerValue)
				Com_Printf("%s: dropped a message fragment at %i\n", NET_AddressToString(netChan->remoteAddress), sequence);

			return false;
		}

		// If it has a bad fragment size, dump it
		if (fragmentSize < 0 || fragmentSize > FRAGMENT_SIZE){
			if (net_showDrop->integerValue)
				Com_Printf("%s: illegal message fragment size at %i\n", NET_AddressToString(netChan->remoteAddress), sequence);

			return false;
		}

		// Copy the fragment to the fragment buffer
		if (netChan->incomingFragmentSize + fragmentSize > sizeof(netChan->incomingFragmentBuffer) || msg->readBytes + fragmentSize > msg->writeBytes){
			if (net_showDrop->integerValue)
				Com_Printf("%s: illegal message fragment size at %i\n", NET_AddressToString(netChan->remoteAddress), sequence);

			return false;
		}

		MSG_ReadData(msg, netChan->incomingFragmentBuffer + netChan->incomingFragmentSize, fragmentSize);

		netChan->incomingFragmentSize += fragmentSize;

		// If this wasn't the last fragment, don't process anything
		if (fragmentSize == FRAGMENT_SIZE)
			return false;

		// Decompress the message data
		if (Huff_Decompress(netChan->incomingFragmentBuffer, buffer, size) != netChan->incomingFragmentSize){
			if (net_showDrop->integerValue)
				Com_Printf("%s: failed to decompress message at %i\n", NET_AddressToString(netChan->remoteAddress), sequence);

			return false;
		}

		if (net_showHuffman->integerValue)
			Com_Printf("%s decompress: %4i -> %4i\n", net_sourceString[netChan->sock], netChan->incomingFragmentSize, size);

		// Clear the write state
		MSG_ClearWriteState(msg);

		// Write the sequence number
		MSG_WriteLong(msg, sequence);

		// Write the uncompressed message data
		MSG_WriteData(msg, buffer, size);

		// The message can now be read from the current message pointer
		netChan->incomingSequence = sequence;

		return true;
	}

	// Decompress the message data
	if (Huff_Decompress(msg->data + msg->readBytes, buffer, size) != msg->writeBytes - msg->readBytes){
		if (net_showDrop->integerValue)
			Com_Printf("%s: failed to decompress message at %i\n", NET_AddressToString(netChan->remoteAddress), sequence);

		return false;
	}

	if (net_showHuffman->integerValue)
		Com_Printf("%s decompress: %4i -> %4i\n", net_sourceString[netChan->sock], msg->writeBytes - msg->readBytes, size);

	// Clear the write state
	MSG_ClearWriteState(msg);

	// Write the sequence number
	MSG_WriteLong(msg, sequence);

	// Write the uncompressed message data
	MSG_WriteData(msg, buffer, size);

	// The message can now be read from the current message pointer
	netChan->incomingSequence = sequence;

	return true;
}